#pragma once
#include <Math/Vector3.hpp>
#include <Math/Vector4.hpp>
#include <Graphics/Graphics.hpp>
#include <Image/ImageHelper.hpp>

namespace zzz {
#undef RGB  // stupid vc
template<typename T>
class Color : public Vector<4,T> {
public:
  Color(){}
  Color(const Color<T> &other):Vector<4,T>(other){}
  Color(T r, T g, T b, T a){SetRGBA(r,g,b,a);}
  inline Color(T r, T g, T b);  //TIP: need inline, otherwise redefinition will happen
  Color(const Vector<4,T> &a){SetRGBA(a[0],a[1],a[2],a[3]);}
  Color(const Vector<3,T> &a){SetRGB(a[0],a[1],a[2]);}

  using Vector<4,T>::operator=;
  using Vector<4,T>::operator[];
  void Set(const Color<T> &other){Set(other.r(),other.g(),other.g(),other.a());}
  void SetRGBA(T r, T g, T b, T a){v[0]=r; v[1]=g; v[2]=b; v[3]=a;}
  void SetRGB(T r, T g, T b){v[0]=r; v[1]=g; v[2]=b;}
  void SetR(T r){v[0]=r;}
  void SetG(T g){v[1]=g;}
  void SetB(T b){v[2]=b;}
  void SetA(T a){v[3]=a;}

  Vector<3,T> &RGB() {
    return *(reinterpret_cast<Vector<3,T>*>(v));
  }
  const Vector<3,T> &RGB() const {
    return *(reinterpret_cast<const Vector<3,T>*>(v));
  }

  using Vector<4,T>::r;
  using Vector<4,T>::g;
  using Vector<4,T>::b;
  using Vector<4,T>::a;
  using Vector<4,T>::Data;

  inline void ApplyGL() const;

  inline T Brightness() const;
  Vector<3,T> ToHSV() const {
    Vector<3,T> hsv;
    const T minv = RGB().Min();
    const T maxv = RGB().Max();
    hsv.v = maxv;
    const T delta = maxv-minv;
    if (maxv!=0.0) {
      hsv.s = delta/maxv;
    } else {
      // Black
      hsv.h = 0;
      hsv.s = 0;
      return;
    }
    if (delta==0) {
      hsv.h = 0;
      hsv.s = 0;
    } else {
      if (r()==maxv) {
        hsv.h = (g()-b()) / delta;           // between yellow & magenta
      } else {
        if(g()==maxv) hsv.h = 2 + (b()-r())/delta;     // between cyan & yellow
        else hsv.h = 4 + (r()-g())/delta;    // between magenta & cyan
      }
    }
    // Scale h to degrees
    hsv.h *= 60;
    if (hsv.h<0.0) hsv.h += 360;
  }

  void FromHSV(const Vector<3,T> &hsv) {
    if (hsv.s==0) {
      r() = g() = b() = hsv.v;
      return;
    }
    const T hue = hsv.h/60.0;    // sector 0 to 5
    const int i = int(floor(hue));
    const T f   = hue-i;     // factorial part of h
    const T p = hsv.v*(1-hsv.s);
    const T q = hsv.v*(1-hsv.s*f);
    const T t = hsv.v*(1-hsv.s*(1-f));
    switch (i) {
    case 0:
      Set(hsv.v,t,p);
      break;
    case 1:
      Set(q,hsv.v,p);
      break;
    case 2:
      Set(p,hsv.v,t);
      break;
    case 3:
      Set(p,q,hsv.v);
      break;
    case 4:
      Set(t,p,hsv.v);
      break;
    default:        // case 5:
      Set(hsv.v,p,q);
      break;
    }
  }

  void FromXYZ(const Vector<3,T> &c)
  {
    r()=3.2410*c[0]-1.5374*c[1]-0.4986*c[2];
    g()=-0.9692*c[0]+1.8760*c[1]+0.0416*c[2];
    b()=0.0556*c[0]-0.2040*c[1]+1.0570*c[2];
  }
  Vector<3,T> ToXYZ() const
  {
    return Vector<3,T>(\
          0.4124*r()+0.3576*g()+0.1805*b(),\
          0.2126*r()+0.7152*g()+0.0722*b(),\
          0.0193*r()+0.1192*g()+0.9505*b());
  }
  bool IsGray(){return r()==g() && g()==b();}

  template<typename TO>
  Color<TO> ToColor() const {
    return Color<TO>(ConvertPixel< Vector<4,T>,Vector<4,TO> >(*this));
  }

  template<typename TO>
  Vector<3,TO> ToRGB() const {
    return Color<TO>(ConvertPixel< Vector<4,T>,Vector<4,TO> >(*this)).RGB();
  }

  private:
    using Vector<4,T>::v;
};

template<>
Color<float>::Color(float r, float g, float b)
{
  SetRGBA(r,g,b,1.0f);
}
template<>
Color<double>::Color(double r, double g, double b)
{
  SetRGBA(r,g,b,1.0);
}
template<>
Color<zubyte>::Color(zubyte r, zubyte g, zubyte b)
{
  SetRGBA(r,g,b,255);
}

template<>
void Color<float>::ApplyGL() const
{
  glColor4fv(v);
}
template<>
void Color<double>::ApplyGL() const
{
  glColor4dv(v);
}
template<>
void Color<zuchar>::ApplyGL() const
{
  glColor4ubv(v);
}
template<>
float Color<float>::Brightness() const
{
  return 0.212671f*r() + 0.715160f*g() + 0.072169f*b();
}
template<>
double Color<double>::Brightness() const
{
  return 0.212671*r() + 0.715160*g() + 0.072169*b();
}
template<>
zubyte Color<zubyte>::Brightness() const
{
  return (zubyte)(0.212671*r() + 0.715160*g() + 0.072169*b());
}

typedef Color<zfloat32> Colorf32;
typedef Color<zfloat64> Colorf64;
typedef Color<zuint8> Colorui8;

typedef Color<float> Colorf;
typedef Color<double> Colord;
typedef Color<zuchar> Coloruc;

SIMPLE_IOOBJECT(Colorf);
SIMPLE_IOOBJECT(Colord);
SIMPLE_IOOBJECT(Coloruc);

}
